package org.shj.db;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.Date;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import org.shj.util.PropertyUtils;
import org.shj.util.StringUtils;

public class DbConnect {

	private String driver;
	
	private String url;
	
	private String password;
	
	private String username;
	
	private static DbConnect dbConnect;
	
	private static volatile boolean init = false;
	
	private static Object lock = new Object();
	
	private List<Connection> conns = new LinkedList<>();
	
	private DbConnect(){}
	
	public static DbConnect getInstance(){
		if(!init){
			synchronized(lock){
				if(!init){
					dbConnect = new DbConnect();
					dbConnect.init();
				}
			}
		}
		return dbConnect;
	}
	
//	private List<>
	
	public void init(){
		this.url = PropertyUtils.getInstance().get("driver.url");
		this.driver = PropertyUtils.getInstance().get("driver.class");
		this.username = PropertyUtils.getInstance().get("driver.username");
		this.password = PropertyUtils.getInstance().get("driver.password");
		
		try{
			Class clazz = Class.forName(driver);
			for(int i = 0; i < 10; i++){
				conns.add(createConnection());
			}
		}catch(Exception e){
			System.err.println("create connection failed.");
			for(Connection conn : conns){
				try{
				conn.close();
				}catch(Exception ee){
					ee.printStackTrace();
				}
			}
		}
	}
	
	private Connection createConnection() throws Exception{
		
		Connection conn = DriverManager.getConnection(this.url);
		return conn;
	}
	
	public Connection getConn() throws Exception{
		if(conns.isEmpty()){
			return createConnection() ;
		}else{
			return conns.remove(0);
		}
	}
	
	public boolean update(String sql, Object ... params) {
		Connection conn = null;
		try{
			System.out.println("Sql: " + sql);
			conn = getConn();
			getPreparedStatement(conn, sql, params).execute();
		}catch(Exception e){
			e.printStackTrace();
			return false;
		}finally{
			closeConnection(conn);
		}
		return true;
	}
	
	public <T> List<T> getResultsBySql(String sql, Class<T> clazz, Object ... params) {
		Connection conn = null;
		try{
			conn = getConn();
			ResultSet rs = getPreparedStatement(conn,sql, params).executeQuery();
			List<T> results = new ArrayList<T>();
			Field[] fields = clazz.getDeclaredFields();
			Method set;
			while(rs.next()){
				T rlt = clazz.newInstance();
				for(Field f : fields){
					set = clazz.getMethod("set"+ StringUtils.capFirst(f.getName()), f.getType());
					set.invoke(rlt, rs.getObject(StringUtils.convert2ColNm(f.getName()), f.getType()));
				}
				results.add(rlt);
			}
			return results;
		}catch(Exception e){
			e.printStackTrace();
			return null;
		}finally{
			closeConnection(conn);
		}
	}
	
	private void closeConnection(Connection conn){
		if(conn == null){
			return;
		}
		if(conns.size() < 10){
			conns.add(conn);
		}else{
			try{
			conn.close();
			}catch(Exception e){
				e.printStackTrace();
			}
		}
	}
	
	private PreparedStatement getPreparedStatement(Connection conn,String sql, Object ... params) throws Exception{
		
		PreparedStatement ps = conn.prepareStatement(sql);
		for(int i = 1; i <= params.length; i++){
			Object param = params[i-1];
			if(param instanceof String){
				ps.setString(i, (String)param);
			}else if(param instanceof Date){
				ps.setTimestamp(i, new Timestamp(((Date)param).getTime()));
			}else if(param instanceof java.util.Date){
				java.util.Date paramDt = (java.util.Date)param;
				ps.setTimestamp(i, new Timestamp(paramDt.getTime()));
			}else if(param instanceof Long){
				ps.setLong(i, (Long)param);
			}else if(param instanceof Integer){
				ps.setInt(i, (Integer)param);
			}else if(param instanceof BigDecimal){
				ps.setBigDecimal(i, (BigDecimal)param);
			}else if(param instanceof Float){
				ps.setFloat(i, (Float)param);
			}else if(param instanceof Double){
				ps.setDouble(i, (Double)param);
			}else{
				ps.setString(i, (String)param);
			}
		}
		return ps;
	}
}
